package com.mdp.workflow.biz.re.service;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mdp.core.api.CacheHKVService;
import com.mdp.core.entity.LangTips;
import com.mdp.core.err.BizException;
import com.mdp.safe.client.entity.User;
import com.mdp.safe.client.utils.LoginUtils;
import com.mdp.workflow.api.FlowModelService;
import com.mdp.workflow.api.FlowProcdefService;
import com.mdp.workflow.biz.re.mapper.ModelMapper;
import com.mdp.workflow.biz.re.vo.ModelVo;
import com.mdp.workflow.bus.candidate.TaskCandidateInvoker;
import com.mdp.workflow.comm.tools.FlowModelUtils;
import com.mdp.workflow.comm.tools.FlowableUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Service("mdp.workflow.re.FlowModelServiceImpl")
public class FlowModelServiceImpl implements FlowModelService {

	static Logger logger =LoggerFactory.getLogger(FlowModelServiceImpl.class);

	@Resource
	private RepositoryService repositoryService;
	@Resource
	private FlowProcdefService flowProcdefService;

	@Resource
	private TaskCandidateInvoker taskCandidateInvoker;

	@Autowired
	ModelMapper modelMapper;

	@Autowired
	CacheHKVService cacheHKVService;


	@Override
	@Transactional(rollbackFor = Exception.class)
	public String createModel( ModelVo modelVo) {
		String bpmnXml=modelVo.getBpmnXml();
		BpmnModel bpmnModel=FlowModelUtils.getBpmnModel(bpmnXml.getBytes(StandardCharsets.UTF_8));
		Process process=  bpmnModel.getMainProcess();
		if(process==null){
			throw new BizException("id-required","主流程未找到，有可能是未设置流程标识");
		}
		String id = process.getId();
		if(ObjectUtils.isEmpty(id)||"undefined".equals(id)||"null".equals(id)){
			throw new BizException("id-required","流程标识不能为空");
		}
		String name=bpmnModel.getMainProcess().getName();
		if(ObjectUtils.isEmpty(name)||"undefined".equals(name)||"null".equals(name)){
			throw new BizException("name-required","流程名称不能为空");
		}
		// 校验流程标识已经存在
		org.flowable.engine.repository.Model keyModel = getModelByKey(id);
		if (keyModel != null) {
 			throw new BizException("MODEL_KEY_EXISTS","已经存在流程标识为【%s】的流程", id);
		}

		// 校验流程名称已经存在
		List<Model> nameModel = repositoryService.createModelQuery().modelName(name).modelTenantId(FlowableUtils.getTenantId()).list();
		if(nameModel!=null && nameModel.size()>0){
			throw new BizException("MODEL_NAME_EXISTS","已经存在流程名称为【%s】的流程", name);
		}

		// 创建流程定义
		org.flowable.engine.repository.Model model = repositoryService.newModel();
		model.setName(name);
		model.setVersion(1);
		model.setTenantId(FlowableUtils.getTenantId());
		model.setCategory(bpmnModel.getMainProcess().getAttributeValue(null,"category"));
		model.setKey(id);
		// 保存流程定义
		repositoryService.saveModel(model);
		// 保存 BPMN XML
		saveModelBpmnXml(model.getId(), bpmnXml);
		clearCache(model.getKey());
		return model.getId();
	}


	@Override
	@Transactional(rollbackFor = Exception.class) // 因为进行多个操作，所以开启事务
	public void updateModel(ModelVo updateReqVO ) {
		// 校验流程模型存在
		org.flowable.engine.repository.Model model = getModel(updateReqVO.getModel().getId());
		if (model == null) {
			throw new BizException("MODEL_NOT_EXISTS","模型不存在");
		}
		if(!FlowableUtils.getTenantId().equals(model.getTenantId())){
			throw new BizException("branchId-err","模型属于%s,您无权修改",model.getTenantId());
		}
		BeanUtils.copyProperties(updateReqVO.getModel(),model);
		if(!ObjectUtils.isEmpty(updateReqVO.getBpmnXml())){
			BpmnModel bpmnModel=FlowModelUtils.getBpmnModel(updateReqVO.getBpmnXml().getBytes(StandardCharsets.UTF_8));
			Process process=  bpmnModel.getMainProcess();
			if(process==null){
				throw new BizException("id-required","主流程未找到，有可能是未设置流程标识");
			}
			String id = process.getId();
			if(ObjectUtils.isEmpty(id)||"undefined".equals(id)||"null".equals(id)){
				throw new BizException("id-required","流程标识不能为空");
			}
			String name=bpmnModel.getMainProcess().getName();
			if(ObjectUtils.isEmpty(name)||"undefined".equals(name)||"null".equals(name)){
				throw new BizException("name-required","流程名称不能为空");
			}
			model.setName(name);
			model.setCategory(bpmnModel.getMainProcess().getAttributeValue(null,"category"));

		}


		// 更新模型
		repositoryService.saveModel(model);
		// 更新 BPMN XML
		saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml());


		clearCache(model.getKey());
	}

	@Override
	@Transactional(rollbackFor = Exception.class) // 因为进行多个操作，所以开启事务
	public void deployModel(String id) {
		// 1.1 校验流程模型存在
		org.flowable.engine.repository.Model model = getModel(id);
		if (ObjectUtils.isEmpty(model)) {
			throw new BizException("MODEL_NOT_EXISTS","模型不存在");
		}
		// 1.2 校验流程图
		byte[] bpmnBytes = getModelBpmnXML(model.getId());
		validateBpmnXml(bpmnBytes);
		// 1.3 校验表单已配

		// 1.4 校验任务分配规则已配置
		taskCandidateInvoker.validateBpmnConfig(bpmnBytes);

		// 2.1 创建流程定义
		ProcessDefinition definition = flowProcdefService.createProcessDefinition(model, bpmnBytes);

		// 2.2 将老的流程定义进行挂起。也就是说，只有最新部署的流程定义，才可以发起任务。
		updateProcessDefinitionSuspended(model.getDeploymentId());

		// 2.3 更新 model 的 deploymentId，进行关联
 		model.setDeploymentId(definition.getDeploymentId());
		repositoryService.saveModel(model);

		clearCache(model.getKey());
	}

	private void validateBpmnXml(byte[] bpmnBytes) {
		BpmnModel bpmnModel = FlowModelUtils.getBpmnModel(bpmnBytes);
		if (bpmnModel == null) {
			throw new BizException("MODEL_NOT_EXISTS","模型不存在");
		}
		// 1. 没有 StartEvent
		StartEvent startEvent = FlowModelUtils.getStartEvent(bpmnModel);
		if (startEvent == null) {
			throw new BizException("START_EVENT_NOT_EXISTS","没有启动节点，不允许保存");
		}
		// 2. 校验 UserTask 的 name 都配置了
		List<UserTask> userTasks = FlowModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
		userTasks.forEach(userTask -> {
			if (StrUtil.isEmpty(userTask.getName())) {
				throw new BizException(LangTips.errMsg("USER_TASK_NAME_NOT_EXISTS","节点【%s】没有配置节点名称",userTask.getId()));
			}
		});
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void deleteModel(String id) {
		User user = LoginUtils.getCurrentUserInfo();
		// 校验流程模型存在
		org.flowable.engine.repository.Model model = getModel(id);
		if (model == null) {
			throw new BizException("MODEL_NOT_EXISTS","模型不存在");
		}
		if(!user.getBranchId().equals(model.getTenantId())){
			throw new BizException("not-you-branch-model","不是您企业的模型，不允许删除 ");
		}
		// 执行删除
		repositoryService.deleteModel(id);
		// 禁用流程定义
		updateProcessDefinitionSuspended(model.getDeploymentId());

		clearCache(model.getKey());
	}

	@Override
	public void updateModelState(String id, String state) {
		// 1.1 校验流程模型存在
		org.flowable.engine.repository.Model model = getModel(id);
		if (model == null) {
			throw new BizException("MODEL_NOT_EXISTS","模型不存在");
		}
		// 1.2 校验流程定义存在
		ProcessDefinition definition = flowProcdefService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
		if (definition == null) {
			throw new BizException("PROCDEF_NOT_EXISTS","流程定义不存在");
		}

		// 2. 更新状态
		flowProcdefService.updateProcessDefinitionState(definition.getId(), state);

		clearCache(model.getKey());
	}

	@Override
	public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
		return repositoryService.getBpmnModel(processDefinitionId);
	}

	@Override
	public List<Map<String, Object>> selectListMapByWhere(IPage page, QueryWrapper  qw, Map<String, Object> params) {
		return modelMapper.selectListMapByWhere(page,qw,params);
	}



	private void saveModelBpmnXml(String id, String bpmnXml) {
		if (StrUtil.isEmpty(bpmnXml)) {
			return;
		}
		repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));

	}

	/**
	 * 挂起 deploymentId 对应的流程定义
	 *
	 * 注意：这里一个 deploymentId 只关联一个流程定义
	 *
	 * @param deploymentId 流程发布Id
	 */
	private void updateProcessDefinitionSuspended(String deploymentId) {
		if (StrUtil.isEmpty(deploymentId)) {
			return;
		}
		ProcessDefinition oldDefinition = flowProcdefService.getProcessDefinitionByDeploymentId(deploymentId);
		if (oldDefinition == null) {
			return;
		}
		flowProcdefService.suspend(oldDefinition.getId());
	}

	public org.flowable.engine.repository.Model getModelByKey(String key) {
		return repositoryService.createModelQuery().modelKey(key).latestVersion().singleResult();
	}

	public void   removeByIds( Collection<String> ids) {
		User user = LoginUtils.getCurrentUserInfo();
 		for (Object id : ids) {
			// 校验流程模型存在
			org.flowable.engine.repository.Model model = getModel((String) id);
			if (model == null) {
				throw new BizException("MODEL_NOT_EXISTS","模型不存在");
			}
			if(!user.getBranchId().equals(model.getTenantId())){
				throw new BizException("not-you-branch-model","该模型属于企业【%s】，您无权删除 ",model.getTenantId());
			}
			// 执行删除
			repositoryService.deleteModel((String) id);
			// 禁用流程定义
			updateProcessDefinitionSuspended(model.getDeploymentId());
			clearCache(model.getKey());
		}
	}

	@Override
	public void clearCache(String key) {
		cacheHKVService.remove(FlowModelService.START_PROC_PRE_CACHE_KEY,key);
	}

	@Override
	public org.flowable.engine.repository.Model getModel(String id) {
		return repositoryService.getModel(id);
	}

	@Override
	public byte[] getModelBpmnXML(String id) {
		return repositoryService.getModelEditorSource(id);
	}

}

